About the dataset

Description

The Human Activity Recognition database was built from the recordings of 30 study participants performing activities of daily living (ADL) while carrying a waist-mounted smartphone with embedded inertial sensors. The objective is to classify activities into one of the six activities performed.

Description of experiment

The experiments have been carried out with a group of 30 volunteers within an age bracket of 19-48 years. Each person performed six activities (WALKING, WALKING_UPSTAIRS, WALKING_DOWNSTAIRS, SITTING, STANDING, LAYING) wearing a smartphone (Samsung Galaxy S II) on the waist. Using its embedded accelerometer and gyroscope, we captured 3-axial linear acceleration and 3-axial angular velocity at a constant rate of 50Hz. The experiments have been video-recorded to label the data manually. The obtained dataset has been randomly partitioned into two sets, where 70% of the volunteers was selected for generating the training data and 30% the test data.

The sensor signals (accelerometer and gyroscope) were pre-processed by applying noise filters and then sampled in fixed-width sliding windows of 2.56 sec and 50% overlap (128 readings/window). The sensor acceleration signal, which has gravitational and body motion components, was separated using a Butterworth low-pass filter into body acceleration and gravity. The gravitational force is assumed to have only low frequency components, therefore a filter with 0.3 Hz cutoff frequency was used. From each window, a vector of features was obtained by calculating variables from the time and frequency domain.

Attribute information

For each record in the dataset the following is provided:

  • Triaxial acceleration from the accelerometer (total acceleration) and the estimated body acceleration.
  • Triaxial Angular velocity from the gyroscope.
  • A 561-feature vector with time and frequency domain variables.
  • Its activity label.
  • An identifier of the subject who carried out the experiment.

Relevant papers

  • Davide Anguita, Alessandro Ghio, Luca Oneto, Xavier Parra and Jorge L. Reyes-Ortiz. Human Activity Recognition on Smartphones using a Multiclass Hardware-Friendly Support Vector Machine. International Workshop of Ambient Assisted Living (IWAAL 2012). Vitoria-Gasteiz, Spain. Dec 2012

  • Davide Anguita, Alessandro Ghio, Luca Oneto, Xavier Parra, Jorge L. Reyes-Ortiz. Energy Efficient Smartphone-Based Activity Recognition using Fixed-Point Arithmetic. Journal of Universal Computer Science. Special Issue in Ambient Assisted Living: Home Care. Volume 19, Issue 9. May 2013

  • Davide Anguita, Alessandro Ghio, Luca Oneto, Xavier Parra and Jorge L. Reyes-Ortiz. Human Activity Recognition on Smartphones using a Multiclass Hardware-Friendly Support Vector Machine. 4th International Workshop of Ambient Assited Living, IWAAL 2012, Vitoria-Gasteiz, Spain, December 3-5, 2012. Proceedings. Lecture Notes in Computer Science 2012, pp 216-223.

  • Jorge Luis Reyes-Ortiz, Alessandro Ghio, Xavier Parra-Llanas, Davide Anguita, Joan Cabestany, Andreu Català. Human Activity and Motion Disorder Recognition: Towards Smarter Interactive Cognitive Environments. 21st European Symposium on Artificial Neural Networks, Computational Intelligence and Machine Learning, ESANN 2013. Bruges, Belgium 24-26 April 2013.

Citation

Davide Anguita, Alessandro Ghio, Luca Oneto, Xavier Parra and Jorge L. Reyes-Ortiz. A Public Domain Dataset for Human Activity Recognition Using Smartphones. 21st European Symposium on Artificial Neural Networks, Computational Intelligence and Machine Learning, ESANN 2013. Bruges, Belgium 24-26 April 2013.



Step 1 - Start and connect to a H2O cluster (JVM)

# Pre-load all R packages
suppressPackageStartupMessages(library(data.table))
suppressPackageStartupMessages(library(h2o))
suppressPackageStartupMessages(library(plotly))
# Start and connect to a H2O cluster (JVM)
h2o.init(nthreads = -1)

H2O is not running yet, starting it now...

Note:  In case of errors look at the following log files:
    /tmp/Rtmp9896Ad/h2o_joe_started_from_r.out
    /tmp/Rtmp9896Ad/h2o_joe_started_from_r.err
openjdk version "1.8.0_131"
OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-0ubuntu1.16.04.2-b11)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)

Starting H2O JVM and connecting: . Connection successful!

R is connected to the H2O cluster: 
    H2O cluster uptime:         1 seconds 385 milliseconds 
    H2O cluster version:        3.10.5.1 
    H2O cluster version age:    10 days  
    H2O cluster name:           H2O_started_from_R_joe_knw156 
    H2O cluster total nodes:    1 
    H2O cluster total memory:   5.21 GB 
    H2O cluster total cores:    8 
    H2O cluster allowed cores:  8 
    H2O cluster healthy:        TRUE 
    H2O Connection ip:          localhost 
    H2O Connection port:        54321 
    H2O Connection proxy:       NA 
    H2O Internal Security:      FALSE 
    R Version:                  R version 3.4.0 (2017-04-21) 
h2o.no_progress() # disable progress bar in notebbok


Step 2 - Importing datasets into H2O

# Check if the datasets exist (locally)
chk_train <- suppressMessages(file.exists("./data/train.csv.gz"))
chk_test <- suppressMessages(file.exists("./data/test.csv.gz"))
# Import datasets (locally)
if (chk_train) hex_train <- h2o.importFile("./data/train.csv.gz")
if (chk_test) hex_test <- h2o.importFile("./data/test.csv.gz")
# Import datasets (from GitHub if they are not available locally)
if (!chk_train) hex_train <- h2o.importFile("https://github.com/woobe/h2o_demo_for_ibm_dsx/blob/master/data/train.csv.gz?raw=true")
if (!chk_test) hex_test <- h2o.importFile("https://github.com/woobe/h2o_demo_for_ibm_dsx/blob/master/data/test.csv.gz?raw=true")


Step 3 - Exploratory Analysis

# Dimensions
# 'Train' dataset has 7352 rows and 562 columns
# 'Test' dataset has 2947 rows and 562 columns
dim(hex_train)
[1] 7352  562
dim(hex_test)
[1] 2947  562
# First few records
# First column is the label 'activity'
# Rest of the columns (V1 to V561) are sensors data
head(hex_train)
head(hex_test)
# Look at 'activity' column
# Six classes (Carinality = 6)
# No missing value
h2o.describe(hex_train$activity)
h2o.describe(hex_test$activity)
# Extract 'activity' columns for other graphics packages in R
d_activity_train <- as.data.frame(hex_train$activity)
d_activity_test <- as.data.frame(hex_test$activity)
# Count acitivity 
d_freq_train <- as.data.frame(table(d_activity_train))
d_freq_test <- as.data.frame(table(d_activity_test))
d_freq <- merge(d_freq_train, d_freq_test, by.x = "d_activity_train", by.y = "d_activity_test", sort = FALSE)
colnames(d_freq) <- c("activity", "freq_train", "freq_test")
d_freq
# Visualize 'activity' in both 'train' and 'test'
p <- plot_ly(d_freq, x = ~activity, y = ~freq_train, type = 'bar', name = 'Frequency (Train)') %>%
  add_trace(y = ~freq_test, name = 'Frequency (Test)') %>%
  layout(title = "Activities in 'Train' and 'Test' Dataset") %>%
  layout(yaxis = list(title = 'Count'), xaxis = list(title = "")) %>%
  layout(margin = list(b = 90)) %>%
  layout(barmode = "group")
p
# Look at relationship between sensor data `f1_tBodyAccmeanX` and activity
d_f1 <- data.frame(V1_train = as.data.frame(hex_train$f1_tBodyAccmeanX), activity = as.data.frame(hex_train$activity))
head(d_f1)
p <- plot_ly(d_f1, y = ~f1_tBodyAccmeanX, color = ~activity, type = "box") %>%
     layout(title = "Relationship between Sensor Data `f1_tBodyAccmeanX` and Activities") %>%
     layout(yaxis = list(title = 'f1_tBodyAccmeanX'), xaxis = list(title = "")) %>%
     layout(margin = list(b = 90))
p
# Principal Component Analysis
# 95% of variance in original data captured by first five principal components
model_pca <- h2o.prcomp(training_frame = hex_train, 
                    x = 2:562, 
                    model_id = "h2o_pca",
                    k = 5)
model_pca    
Model Details:
==============

H2ODimReductionModel: pca
Model ID:  h2o_pca 
Importance of components: 
                             pc1      pc2      pc3      pc4      pc5
Standard deviation     16.190113 4.587733 1.570451 1.441637 0.980662
Proportion of Variance  0.864715 0.069434 0.008136 0.006856 0.003173
Cumulative Proportion   0.864715 0.934148 0.942285 0.949141 0.952313


H2ODimReductionMetrics: pca

No model metrics available for PCA
# Visualize principle components with activity labels
d_pca <- as.data.frame(h2o.predict(model_pca, hex_train))
d_pca <- data.frame(d_pca, as.data.frame(hex_train$activity))
head(d_pca)
p <- plot_ly(data = d_pca, x = ~PC2, y = ~PC3, color = ~activity, 
             type = "scatter", mode = "markers", marker = list(size = 3)) %>%
     layout(title = "Visualizing Principle Components")
p

From the graph above, we can see that:


Step 4 - Build and evalutate a predictive model using H2O’s Gradient Boosting Machine (GBM) algorithm

# Define target and features for model training
target <- "activity"
features <- setdiff(colnames(hex_train), target) # i.e. using the records of all 561 sensors
# Build a GBM model with cross-validation and early stopping
model <- h2o.gbm(x = features,
                 y = target,
                 training_frame = hex_train,                 
                 model_id = "h2o_gbm",
                 ntrees = 500,
                 learn_rate = 0.05,
                 learn_rate_annealing = 0.999,
                 max_depth = 7,
                 sample_rate = 0.9,
                 col_sample_rate = 0.9,
                 nfolds = 3,
                 fold_assignment = "Stratified",
                 stopping_metric = "logloss",
                 stopping_rounds = 5,
                 score_tree_interval = 10,
                 seed = 1234)
# Print out model summary
model
Model Details:
==============

H2OMultinomialModel: gbm
Model ID:  h2o_gbm 
Model Summary: 
  number_of_trees number_of_internal_trees model_size_in_bytes min_depth max_depth mean_depth min_leaves max_leaves mean_leaves
1             290                     1740             1459657         1         7    6.99655          2         83    55.64828


H2OMultinomialMetrics: gbm
** Reported on training data. **

Training Set Metrics: 
=====================

MSE: (Extract with `h2o.mse`) 1.02967e-11
RMSE: (Extract with `h2o.rmse`) 3.208847e-06
Logloss: (Extract with `h2o.logloss`) 1.142173e-06
Mean Per-Class Error: 0
Confusion Matrix: Extract with `h2o.confusionMatrix(<model>,train = TRUE)`)
=========================================================================
Confusion Matrix: Row labels: Actual class; Column labels: Predicted class
                   LAYING SITTING STANDING WALKING WALKING_DOWNSTAIRS WALKING_UPSTAIRS  Error        Rate
LAYING               1407       0        0       0                  0                0 0.0000 = 0 / 1,407
SITTING                 0    1286        0       0                  0                0 0.0000 = 0 / 1,286
STANDING                0       0     1374       0                  0                0 0.0000 = 0 / 1,374
WALKING                 0       0        0    1226                  0                0 0.0000 = 0 / 1,226
WALKING_DOWNSTAIRS      0       0        0       0                986                0 0.0000 =   0 / 986
WALKING_UPSTAIRS        0       0        0       0                  0             1073 0.0000 = 0 / 1,073
Totals               1407    1286     1374    1226                986             1073 0.0000 = 0 / 7,352

Hit Ratio Table: Extract with `h2o.hit_ratio_table(<model>,train = TRUE)`
=======================================================================
Top-6 Hit Ratios: 
  k hit_ratio
1 1  1.000000
2 2  1.000000
3 3  1.000000
4 4  1.000000
5 5  1.000000
6 6  1.000000



H2OMultinomialMetrics: gbm
** Reported on cross-validation data. **
** 3-fold cross-validation on training data (Metrics computed for combined holdout predictions) **

Cross-Validation Set Metrics: 
=====================

MSE: (Extract with `h2o.mse`) 0.007344104
RMSE: (Extract with `h2o.rmse`) 0.08569775
Logloss: (Extract with `h2o.logloss`) 0.02944961
Mean Per-Class Error: 0.008763655
Hit Ratio Table: Extract with `h2o.hit_ratio_table(<model>,xval = TRUE)`
=======================================================================
Top-6 Hit Ratios: 
  k hit_ratio
1 1  0.990751
2 2  1.000000
3 3  1.000000
4 4  1.000000
5 5  1.000000
6 6  1.000000


Cross-Validation Metrics Summary: 
                               mean           sd  cv_1_valid   cv_2_valid  cv_3_valid
accuracy                 0.99077135 8.7470456E-4   0.9896743       0.9925  0.99013966
err                     0.009228656 8.7470456E-4 0.010325655       0.0075 0.009860313
err_count                 22.666666    2.4037008        26.0         18.0        24.0
logloss                 0.029395567 0.0018986394  0.03290966   0.02639239  0.02888465
max_per_class_error     0.032861423 0.0031006113 0.034632035   0.03712297 0.026829269
mean_per_class_accuracy   0.9912118  8.688647E-4   0.9903684    0.9929493  0.99031764
mean_per_class_error    0.008788202  8.688647E-4 0.009631551 0.0070507196 0.009682334
mse                     0.007332566  5.250083E-4 0.007918376  0.006284995 0.007794328
r2                       0.99743503 1.6752906E-4  0.99722123   0.99776536  0.99731845
rmse                     0.08551624  0.003125672  0.08898526   0.07927796  0.08828549
# Look at variable importance in this GBM model
h2o.varimp(model)
Variable Importances: 
                variable relative_importance scaled_importance percentage
1    f53_tGravityAccminX         9839.461914          1.000000   0.204901
2 f560_angleYgravityMean         3236.193359          0.328899   0.067392
3       f10_tBodyAccmaxX         3065.950928          0.311597   0.063847
4 f167_tBodyGyroJerkmadX         2081.444580          0.211540   0.043345
5   f41_tGravityAccmeanX         1930.086060          0.196158   0.040193

---
                           variable relative_importance scaled_importance percentage
556   f494_fBodyGyrobandsEnergy4148            0.008722          0.000001   0.000000
557   f471_fBodyGyrobandsEnergy3348            0.007785          0.000001   0.000000
558         f99_tBodyAccJerkenergyZ            0.004651          0.000000   0.000000
559         f98_tBodyAccJerkenergyY            0.003739          0.000000   0.000000
560        f362_fBodyAccJerkenergyY            0.002965          0.000000   0.000000
561 f548_fBodyBodyGyroJerkMagenergy            0.002210          0.000000   0.000000
# Visualize variable importance
h2o.varimp_plot(model, num_of_features = 15)


Step 5 - Make and evalutate predictions

# Make predictions
yhat_test <- h2o.predict(model, hex_test)
head(yhat_test)
# Evaluate predictions
h2o.performance(model, newdata = hex_test)
H2OMultinomialMetrics: gbm

Test Set Metrics: 
=====================

MSE: (Extract with `h2o.mse`) 0.06358653
RMSE: (Extract with `h2o.rmse`) 0.2521637
Logloss: (Extract with `h2o.logloss`) 0.3214465
Mean Per-Class Error: 0.07575778
Confusion Matrix: Extract with `h2o.confusionMatrix(<model>, <data>)`)
=========================================================================
Confusion Matrix: Row labels: Actual class; Column labels: Predicted class
                   LAYING SITTING STANDING WALKING WALKING_DOWNSTAIRS WALKING_UPSTAIRS  Error          Rate
LAYING                537       0        0       0                  0                0 0.0000 =     0 / 537
SITTING                 0     401       89       0                  0                1 0.1833 =    90 / 491
STANDING                0      40      492       0                  0                0 0.0752 =    40 / 532
WALKING                 0       0        0     481                  4               11 0.0302 =    15 / 496
WALKING_DOWNSTAIRS      0       0        0      10                378               32 0.1000 =    42 / 420
WALKING_UPSTAIRS        0       1        0      24                  6              440 0.0658 =    31 / 471
Totals                537     442      581     515                388              484 0.0740 = 218 / 2,947

Hit Ratio Table: Extract with `h2o.hit_ratio_table(<model>, <data>)`
=======================================================================
Top-6 Hit Ratios: 
  k hit_ratio
1 1  0.926026
2 2  0.990159
3 3  0.994231
4 4  0.996607
5 5  1.000000
6 6  1.000000

As expected: - It is easy to classify Laying - It is difficult to distinguish between Sitting and Standing


Step 6 - Export the PCA and GBM models for Shiny applications

h2o.saveModel(model_pca, path = "./models")
h2o.saveModel(model, path = "./models")


LS0tCnRpdGxlOiAnSDJPIERlbW86IEh1bWFuIEFjdGl2aXR5IFJlY29nbml0aW9uIHdpdGggU21hcnRwaG9uZXMnCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgZmlnX2hlaWdodDogNgogICAgZmlnX3dpZHRoOiA5CiAgICBoaWdobGlnaHQ6IGhhZGRvY2sKICAgIHRoZW1lOiBzcGFjZWxhYgogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tCgojIyBBYm91dCB0aGUgZGF0YXNldAoKLSBSZWNvcmRpbmdzIG9mIDMwIHN0dWR5IHBhcnRpY2lwYW50cyBwZXJmb3JtaW5nIGFjdGl2aXRpZXMgb2YgZGFpbHkgbGl2aW5nCi0gYnkgVUNJIE1hY2hpbmUgTGVhcm5pbmcKLSBSZWZlcmVuY2UgKE9yaWdpbmFsKTogaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL2RhdGFzZXRzL0h1bWFuK0FjdGl2aXR5K1JlY29nbml0aW9uK1VzaW5nK1NtYXJ0cGhvbmVzCi0gUmVmZXJlbmNlIChLYWdnbGUpOiBodHRwczovL3d3dy5rYWdnbGUuY29tL3VjaW1sL2h1bWFuLWFjdGl2aXR5LXJlY29nbml0aW9uLXdpdGgtc21hcnRwaG9uZXMKCgojIyMgRGVzY3JpcHRpb24KClRoZSBIdW1hbiBBY3Rpdml0eSBSZWNvZ25pdGlvbiBkYXRhYmFzZSB3YXMgYnVpbHQgZnJvbSB0aGUgcmVjb3JkaW5ncyBvZiAzMCBzdHVkeSBwYXJ0aWNpcGFudHMgcGVyZm9ybWluZyBhY3Rpdml0aWVzIG9mIGRhaWx5IGxpdmluZyAoQURMKSB3aGlsZSBjYXJyeWluZyBhIHdhaXN0LW1vdW50ZWQgc21hcnRwaG9uZSB3aXRoIGVtYmVkZGVkIGluZXJ0aWFsIHNlbnNvcnMuIFRoZSBvYmplY3RpdmUgaXMgdG8gY2xhc3NpZnkgYWN0aXZpdGllcyBpbnRvIG9uZSBvZiB0aGUgc2l4IGFjdGl2aXRpZXMgcGVyZm9ybWVkLgoKIyMjIERlc2NyaXB0aW9uIG9mIGV4cGVyaW1lbnQKClRoZSBleHBlcmltZW50cyBoYXZlIGJlZW4gY2FycmllZCBvdXQgd2l0aCBhIGdyb3VwIG9mIDMwIHZvbHVudGVlcnMgd2l0aGluIGFuIGFnZSBicmFja2V0IG9mIDE5LTQ4IHllYXJzLiBFYWNoIHBlcnNvbiBwZXJmb3JtZWQgc2l4IGFjdGl2aXRpZXMgKFdBTEtJTkcsIFdBTEtJTkdfVVBTVEFJUlMsIFdBTEtJTkdfRE9XTlNUQUlSUywgU0lUVElORywgU1RBTkRJTkcsIExBWUlORykgd2VhcmluZyBhIHNtYXJ0cGhvbmUgKFNhbXN1bmcgR2FsYXh5IFMgSUkpIG9uIHRoZSB3YWlzdC4gVXNpbmcgaXRzIGVtYmVkZGVkIGFjY2VsZXJvbWV0ZXIgYW5kIGd5cm9zY29wZSwgd2UgY2FwdHVyZWQgMy1heGlhbCBsaW5lYXIgYWNjZWxlcmF0aW9uIGFuZCAzLWF4aWFsIGFuZ3VsYXIgdmVsb2NpdHkgYXQgYSBjb25zdGFudCByYXRlIG9mIDUwSHouIFRoZSBleHBlcmltZW50cyBoYXZlIGJlZW4gdmlkZW8tcmVjb3JkZWQgdG8gbGFiZWwgdGhlIGRhdGEgbWFudWFsbHkuIFRoZSBvYnRhaW5lZCBkYXRhc2V0IGhhcyBiZWVuIHJhbmRvbWx5IHBhcnRpdGlvbmVkIGludG8gdHdvIHNldHMsIHdoZXJlIDcwJSBvZiB0aGUgdm9sdW50ZWVycyB3YXMgc2VsZWN0ZWQgZm9yIGdlbmVyYXRpbmcgdGhlIHRyYWluaW5nIGRhdGEgYW5kIDMwJSB0aGUgdGVzdCBkYXRhLgoKVGhlIHNlbnNvciBzaWduYWxzIChhY2NlbGVyb21ldGVyIGFuZCBneXJvc2NvcGUpIHdlcmUgcHJlLXByb2Nlc3NlZCBieSBhcHBseWluZyBub2lzZSBmaWx0ZXJzIGFuZCB0aGVuIHNhbXBsZWQgaW4gZml4ZWQtd2lkdGggc2xpZGluZyB3aW5kb3dzIG9mIDIuNTYgc2VjIGFuZCA1MCUgb3ZlcmxhcCAoMTI4IHJlYWRpbmdzL3dpbmRvdykuIFRoZSBzZW5zb3IgYWNjZWxlcmF0aW9uIHNpZ25hbCwgd2hpY2ggaGFzIGdyYXZpdGF0aW9uYWwgYW5kIGJvZHkgbW90aW9uIGNvbXBvbmVudHMsIHdhcyBzZXBhcmF0ZWQgdXNpbmcgYSBCdXR0ZXJ3b3J0aCBsb3ctcGFzcyBmaWx0ZXIgaW50byBib2R5IGFjY2VsZXJhdGlvbiBhbmQgZ3Jhdml0eS4gVGhlIGdyYXZpdGF0aW9uYWwgZm9yY2UgaXMgYXNzdW1lZCB0byBoYXZlIG9ubHkgbG93IGZyZXF1ZW5jeSBjb21wb25lbnRzLCB0aGVyZWZvcmUgYSBmaWx0ZXIgd2l0aCAwLjMgSHogY3V0b2ZmIGZyZXF1ZW5jeSB3YXMgdXNlZC4gRnJvbSBlYWNoIHdpbmRvdywgYSB2ZWN0b3Igb2YgZmVhdHVyZXMgd2FzIG9idGFpbmVkIGJ5IGNhbGN1bGF0aW5nIHZhcmlhYmxlcyBmcm9tIHRoZSB0aW1lIGFuZCBmcmVxdWVuY3kgZG9tYWluLgoKIyMjIEF0dHJpYnV0ZSBpbmZvcm1hdGlvbgoKRm9yIGVhY2ggcmVjb3JkIGluIHRoZSBkYXRhc2V0IHRoZSBmb2xsb3dpbmcgaXMgcHJvdmlkZWQ6CgotIFRyaWF4aWFsIGFjY2VsZXJhdGlvbiBmcm9tIHRoZSBhY2NlbGVyb21ldGVyICh0b3RhbCBhY2NlbGVyYXRpb24pIGFuZCB0aGUgZXN0aW1hdGVkIGJvZHkgYWNjZWxlcmF0aW9uLgotIFRyaWF4aWFsIEFuZ3VsYXIgdmVsb2NpdHkgZnJvbSB0aGUgZ3lyb3Njb3BlLgotIEEgNTYxLWZlYXR1cmUgdmVjdG9yIHdpdGggdGltZSBhbmQgZnJlcXVlbmN5IGRvbWFpbiB2YXJpYWJsZXMuCi0gSXRzIGFjdGl2aXR5IGxhYmVsLgotIEFuIGlkZW50aWZpZXIgb2YgdGhlIHN1YmplY3Qgd2hvIGNhcnJpZWQgb3V0IHRoZSBleHBlcmltZW50LgoKIyMjIFJlbGV2YW50IHBhcGVycwoKLSBEYXZpZGUgQW5ndWl0YSwgQWxlc3NhbmRybyBHaGlvLCBMdWNhIE9uZXRvLCBYYXZpZXIgUGFycmEgYW5kIEpvcmdlIEwuIFJleWVzLU9ydGl6LiBIdW1hbiBBY3Rpdml0eSBSZWNvZ25pdGlvbiBvbiBTbWFydHBob25lcyB1c2luZyBhIE11bHRpY2xhc3MgSGFyZHdhcmUtRnJpZW5kbHkgU3VwcG9ydCBWZWN0b3IgTWFjaGluZS4gSW50ZXJuYXRpb25hbCBXb3Jrc2hvcCBvZiBBbWJpZW50IEFzc2lzdGVkIExpdmluZyAoSVdBQUwgMjAxMikuIFZpdG9yaWEtR2FzdGVpeiwgU3BhaW4uIERlYyAyMDEyCgotIERhdmlkZSBBbmd1aXRhLCBBbGVzc2FuZHJvIEdoaW8sIEx1Y2EgT25ldG8sIFhhdmllciBQYXJyYSwgSm9yZ2UgTC4gUmV5ZXMtT3J0aXouIEVuZXJneSBFZmZpY2llbnQgU21hcnRwaG9uZS1CYXNlZCBBY3Rpdml0eSBSZWNvZ25pdGlvbiB1c2luZyBGaXhlZC1Qb2ludCBBcml0aG1ldGljLiBKb3VybmFsIG9mIFVuaXZlcnNhbCBDb21wdXRlciBTY2llbmNlLiBTcGVjaWFsIElzc3VlIGluIEFtYmllbnQgQXNzaXN0ZWQgTGl2aW5nOiBIb21lIENhcmUuIFZvbHVtZSAxOSwgSXNzdWUgOS4gTWF5IDIwMTMKCi0gRGF2aWRlIEFuZ3VpdGEsIEFsZXNzYW5kcm8gR2hpbywgTHVjYSBPbmV0bywgWGF2aWVyIFBhcnJhIGFuZCBKb3JnZSBMLiBSZXllcy1PcnRpei4gSHVtYW4gQWN0aXZpdHkgUmVjb2duaXRpb24gb24gU21hcnRwaG9uZXMgdXNpbmcgYSBNdWx0aWNsYXNzIEhhcmR3YXJlLUZyaWVuZGx5IFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUuIDR0aCBJbnRlcm5hdGlvbmFsIFdvcmtzaG9wIG9mIEFtYmllbnQgQXNzaXRlZCBMaXZpbmcsIElXQUFMIDIwMTIsIFZpdG9yaWEtR2FzdGVpeiwgU3BhaW4sIERlY2VtYmVyIDMtNSwgMjAxMi4gUHJvY2VlZGluZ3MuIExlY3R1cmUgTm90ZXMgaW4gQ29tcHV0ZXIgU2NpZW5jZSAyMDEyLCBwcCAyMTYtMjIzLgoKLSBKb3JnZSBMdWlzIFJleWVzLU9ydGl6LCBBbGVzc2FuZHJvIEdoaW8sIFhhdmllciBQYXJyYS1MbGFuYXMsIERhdmlkZSBBbmd1aXRhLCBKb2FuIENhYmVzdGFueSwgQW5kcmV1IENhdGFsw6AuIEh1bWFuIEFjdGl2aXR5IGFuZCBNb3Rpb24gRGlzb3JkZXIgUmVjb2duaXRpb246IFRvd2FyZHMgU21hcnRlciBJbnRlcmFjdGl2ZSBDb2duaXRpdmUgRW52aXJvbm1lbnRzLiAyMXN0IEV1cm9wZWFuIFN5bXBvc2l1bSBvbiBBcnRpZmljaWFsIE5ldXJhbCBOZXR3b3JrcywgQ29tcHV0YXRpb25hbCBJbnRlbGxpZ2VuY2UgYW5kIE1hY2hpbmUgTGVhcm5pbmcsIEVTQU5OIDIwMTMuIEJydWdlcywgQmVsZ2l1bSAyNC0yNiBBcHJpbCAyMDEzLgoKIyMjIENpdGF0aW9uCgpEYXZpZGUgQW5ndWl0YSwgQWxlc3NhbmRybyBHaGlvLCBMdWNhIE9uZXRvLCBYYXZpZXIgUGFycmEgYW5kIEpvcmdlIEwuIFJleWVzLU9ydGl6LiBBIFB1YmxpYyBEb21haW4gRGF0YXNldCBmb3IgSHVtYW4gQWN0aXZpdHkgUmVjb2duaXRpb24gVXNpbmcgU21hcnRwaG9uZXMuIDIxc3QgRXVyb3BlYW4gU3ltcG9zaXVtIG9uIEFydGlmaWNpYWwgTmV1cmFsIE5ldHdvcmtzLCBDb21wdXRhdGlvbmFsIEludGVsbGlnZW5jZSBhbmQgTWFjaGluZSBMZWFybmluZywgRVNBTk4gMjAxMy4gQnJ1Z2VzLCBCZWxnaXVtIDI0LTI2IEFwcmlsIDIwMTMuCgo8aHI+Cgo8YnI+CgpgYGB7ciwgZWNobyA9IEZBTFNFfQojIEhpZGRlbiBTdGVwIDAKIyBDaGVjayBhbmQgbWFrZSBzdXJlIEgyTyB2ZXJzaW9uIDMuMTAuNS4xIGlzIGluc3RhbGxlZApwa2dfaW5zdGFsbGVkIDwtIGFzLmRhdGEuZnJhbWUoaW5zdGFsbGVkLnBhY2thZ2VzKCksIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKcm93X2gybyA8LSB3aGljaChwa2dfaW5zdGFsbGVkJFBhY2thZ2UgPT0gImgybyIpCmlmIChyb3dfaDJvICE9IDApIHZlcl9oMm8gPC0gcGtnX2luc3RhbGxlZFtyb3dfaDJvLF0kVmVyc2lvbgoKaWYgKChyb3dfaDJvID09IDApIHwgKHZlcl9oMm8gIT0gIjMuMTAuNS4xIikpIHsKICAKICAjIEluc3RhbGwgSDJPIHZlcnNpb24gMy4xMC41LjEKCiAgIyBUaGUgZm9sbG93aW5nIHR3byBjb21tYW5kcyByZW1vdmUgYW55IHByZXZpb3VzbHkgaW5zdGFsbGVkIEgyTyBwYWNrYWdlcyBmb3IgUi4KICBpZiAoInBhY2thZ2U6aDJvIiAlaW4lIHNlYXJjaCgpKSB7IGRldGFjaCgicGFja2FnZTpoMm8iLCB1bmxvYWQ9VFJVRSkgfQogIGlmICgiaDJvIiAlaW4lIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkgeyByZW1vdmUucGFja2FnZXMoImgybyIpIH0KICAKICAjIE5leHQsIHdlIGRvd25sb2FkIHBhY2thZ2VzIHRoYXQgSDJPIGRlcGVuZHMgb24uCiAgcGtncyA8LSBjKCJzdGF0bW9kIiwiUkN1cmwiLCJqc29ubGl0ZSIpCiAgZm9yIChwa2cgaW4gcGtncykgewogICAgaWYgKCEgKHBrZyAlaW4lIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkpIHsgaW5zdGFsbC5wYWNrYWdlcyhwa2cpIH0KICB9CiAgCiAgIyBOb3cgd2UgZG93bmxvYWQsIGluc3RhbGwgYW5kIGluaXRpYWxpemUgdGhlIEgyTyBwYWNrYWdlIGZvciBSLgogIGluc3RhbGwucGFja2FnZXMoImgybyIsIHR5cGU9InNvdXJjZSIsIHJlcG9zPSJodHRwOi8vaDJvLXJlbGVhc2UuczMuYW1hem9uYXdzLmNvbS9oMm8vcmVsLXZhamRhLzEvUiIpCiAgCn0KCmBgYAoKIyMgU3RlcCAxIC0gU3RhcnQgYW5kIGNvbm5lY3QgdG8gYSBIMk8gY2x1c3RlciAoSlZNKQoKYGBge3J9CiMgUHJlLWxvYWQgYWxsIFIgcGFja2FnZXMKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZGF0YS50YWJsZSkpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGgybykpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCmBgYAoKYGBge3J9CiMgU3RhcnQgYW5kIGNvbm5lY3QgdG8gYSBIMk8gY2x1c3RlciAoSlZNKQpoMm8uaW5pdChudGhyZWFkcyA9IC0xKQpoMm8ubm9fcHJvZ3Jlc3MoKSAjIGRpc2FibGUgcHJvZ3Jlc3MgYmFyIGluIG5vdGViYm9rCmBgYAoKPGJyPgoKIyMgU3RlcCAyIC0gSW1wb3J0aW5nIGRhdGFzZXRzIGludG8gSDJPCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBDaGVjayBpZiB0aGUgZGF0YXNldHMgZXhpc3QgKGxvY2FsbHkpCmNoa190cmFpbiA8LSBzdXBwcmVzc01lc3NhZ2VzKGZpbGUuZXhpc3RzKCIuL2RhdGEvdHJhaW4uY3N2Lmd6IikpCmNoa190ZXN0IDwtIHN1cHByZXNzTWVzc2FnZXMoZmlsZS5leGlzdHMoIi4vZGF0YS90ZXN0LmNzdi5neiIpKQoKIyBJbXBvcnQgZGF0YXNldHMgKGxvY2FsbHkpCmlmIChjaGtfdHJhaW4pIGhleF90cmFpbiA8LSBoMm8uaW1wb3J0RmlsZSgiLi9kYXRhL3RyYWluLmNzdi5neiIpCmlmIChjaGtfdGVzdCkgaGV4X3Rlc3QgPC0gaDJvLmltcG9ydEZpbGUoIi4vZGF0YS90ZXN0LmNzdi5neiIpCgojIEltcG9ydCBkYXRhc2V0cyAoZnJvbSBHaXRIdWIgaWYgdGhleSBhcmUgbm90IGF2YWlsYWJsZSBsb2NhbGx5KQppZiAoIWNoa190cmFpbikgaGV4X3RyYWluIDwtIGgyby5pbXBvcnRGaWxlKCJodHRwczovL2dpdGh1Yi5jb20vd29vYmUvaDJvX2RlbW9fZm9yX2libV9kc3gvYmxvYi9tYXN0ZXIvZGF0YS90cmFpbi5jc3YuZ3o/cmF3PXRydWUiKQppZiAoIWNoa190ZXN0KSBoZXhfdGVzdCA8LSBoMm8uaW1wb3J0RmlsZSgiaHR0cHM6Ly9naXRodWIuY29tL3dvb2JlL2gyb19kZW1vX2Zvcl9pYm1fZHN4L2Jsb2IvbWFzdGVyL2RhdGEvdGVzdC5jc3YuZ3o/cmF3PXRydWUiKQpgYGAKCjxicj4KCiMjIFN0ZXAgMyAtIEV4cGxvcmF0b3J5IEFuYWx5c2lzCgpgYGB7cn0KIyBEaW1lbnNpb25zCiMgJ1RyYWluJyBkYXRhc2V0IGhhcyA3MzUyIHJvd3MgYW5kIDU2MiBjb2x1bW5zCiMgJ1Rlc3QnIGRhdGFzZXQgaGFzIDI5NDcgcm93cyBhbmQgNTYyIGNvbHVtbnMKZGltKGhleF90cmFpbikKZGltKGhleF90ZXN0KQpgYGAKCmBgYHtyfQojIEZpcnN0IGZldyByZWNvcmRzCiMgRmlyc3QgY29sdW1uIGlzIHRoZSBsYWJlbCAnYWN0aXZpdHknCiMgUmVzdCBvZiB0aGUgY29sdW1ucyAoVjEgdG8gVjU2MSkgYXJlIHNlbnNvcnMgZGF0YQpoZWFkKGhleF90cmFpbikKaGVhZChoZXhfdGVzdCkKYGBgCgoKYGBge3J9CiMgTG9vayBhdCAnYWN0aXZpdHknIGNvbHVtbgojIFNpeCBjbGFzc2VzIChDYXJpbmFsaXR5ID0gNikKIyBObyBtaXNzaW5nIHZhbHVlCmgyby5kZXNjcmliZShoZXhfdHJhaW4kYWN0aXZpdHkpCmgyby5kZXNjcmliZShoZXhfdGVzdCRhY3Rpdml0eSkKYGBgCgoKYGBge3J9CiMgRXh0cmFjdCAnYWN0aXZpdHknIGNvbHVtbnMgZm9yIG90aGVyIGdyYXBoaWNzIHBhY2thZ2VzIGluIFIKZF9hY3Rpdml0eV90cmFpbiA8LSBhcy5kYXRhLmZyYW1lKGhleF90cmFpbiRhY3Rpdml0eSkKZF9hY3Rpdml0eV90ZXN0IDwtIGFzLmRhdGEuZnJhbWUoaGV4X3Rlc3QkYWN0aXZpdHkpCgojIENvdW50IGFjaXRpdml0eSAKZF9mcmVxX3RyYWluIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoZF9hY3Rpdml0eV90cmFpbikpCmRfZnJlcV90ZXN0IDwtIGFzLmRhdGEuZnJhbWUodGFibGUoZF9hY3Rpdml0eV90ZXN0KSkKZF9mcmVxIDwtIG1lcmdlKGRfZnJlcV90cmFpbiwgZF9mcmVxX3Rlc3QsIGJ5LnggPSAiZF9hY3Rpdml0eV90cmFpbiIsIGJ5LnkgPSAiZF9hY3Rpdml0eV90ZXN0Iiwgc29ydCA9IEZBTFNFKQpjb2xuYW1lcyhkX2ZyZXEpIDwtIGMoImFjdGl2aXR5IiwgImZyZXFfdHJhaW4iLCAiZnJlcV90ZXN0IikKZF9mcmVxCmBgYAoKYGBge3IsIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSA2fQojIFZpc3VhbGl6ZSAnYWN0aXZpdHknIGluIGJvdGggJ3RyYWluJyBhbmQgJ3Rlc3QnCnAgPC0gcGxvdF9seShkX2ZyZXEsIHggPSB+YWN0aXZpdHksIHkgPSB+ZnJlcV90cmFpbiwgdHlwZSA9ICdiYXInLCBuYW1lID0gJ0ZyZXF1ZW5jeSAoVHJhaW4pJykgJT4lCiAgYWRkX3RyYWNlKHkgPSB+ZnJlcV90ZXN0LCBuYW1lID0gJ0ZyZXF1ZW5jeSAoVGVzdCknKSAlPiUKICBsYXlvdXQodGl0bGUgPSAiQWN0aXZpdGllcyBpbiAnVHJhaW4nIGFuZCAnVGVzdCcgRGF0YXNldCIpICU+JQogIGxheW91dCh5YXhpcyA9IGxpc3QodGl0bGUgPSAnQ291bnQnKSwgeGF4aXMgPSBsaXN0KHRpdGxlID0gIiIpKSAlPiUKICBsYXlvdXQobWFyZ2luID0gbGlzdChiID0gOTApKSAlPiUKICBsYXlvdXQoYmFybW9kZSA9ICJncm91cCIpCnAKYGBgCgpgYGB7cn0KIyBMb29rIGF0IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHNlbnNvciBkYXRhIGBmMV90Qm9keUFjY21lYW5YYCBhbmQgYWN0aXZpdHkKZF9mMSA8LSBkYXRhLmZyYW1lKFYxX3RyYWluID0gYXMuZGF0YS5mcmFtZShoZXhfdHJhaW4kZjFfdEJvZHlBY2NtZWFuWCksIGFjdGl2aXR5ID0gYXMuZGF0YS5mcmFtZShoZXhfdHJhaW4kYWN0aXZpdHkpKQpoZWFkKGRfZjEpCmBgYAoKYGBge3IsIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSA2fQpwIDwtIHBsb3RfbHkoZF9mMSwgeSA9IH5mMV90Qm9keUFjY21lYW5YLCBjb2xvciA9IH5hY3Rpdml0eSwgdHlwZSA9ICJib3giKSAlPiUKICAgICBsYXlvdXQodGl0bGUgPSAiUmVsYXRpb25zaGlwIGJldHdlZW4gU2Vuc29yIERhdGEgYGYxX3RCb2R5QWNjbWVhblhgIGFuZCBBY3Rpdml0aWVzIikgJT4lCiAgICAgbGF5b3V0KHlheGlzID0gbGlzdCh0aXRsZSA9ICdmMV90Qm9keUFjY21lYW5YJyksIHhheGlzID0gbGlzdCh0aXRsZSA9ICIiKSkgJT4lCiAgICAgbGF5b3V0KG1hcmdpbiA9IGxpc3QoYiA9IDkwKSkKcApgYGAKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMKIyA5NSUgb2YgdmFyaWFuY2UgaW4gb3JpZ2luYWwgZGF0YSBjYXB0dXJlZCBieSBmaXJzdCBmaXZlIHByaW5jaXBhbCBjb21wb25lbnRzCm1vZGVsX3BjYSA8LSBoMm8ucHJjb21wKHRyYWluaW5nX2ZyYW1lID0gaGV4X3RyYWluLCAKICAgICAgICAgICAgICAgICAgICB4ID0gMjo1NjIsIAogICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gImgyb19wY2EiLAogICAgICAgICAgICAgICAgICAgIGsgPSA1KQptb2RlbF9wY2EgICAgCmBgYAoKYGBge3J9CiMgVmlzdWFsaXplIHByaW5jaXBsZSBjb21wb25lbnRzIHdpdGggYWN0aXZpdHkgbGFiZWxzCmRfcGNhIDwtIGFzLmRhdGEuZnJhbWUoaDJvLnByZWRpY3QobW9kZWxfcGNhLCBoZXhfdHJhaW4pKQpkX3BjYSA8LSBkYXRhLmZyYW1lKGRfcGNhLCBhcy5kYXRhLmZyYW1lKGhleF90cmFpbiRhY3Rpdml0eSkpCmhlYWQoZF9wY2EpCmBgYAoKYGBge3IsIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSA2fQpwIDwtIHBsb3RfbHkoZGF0YSA9IGRfcGNhLCB4ID0gflBDMiwgeSA9IH5QQzMsIGNvbG9yID0gfmFjdGl2aXR5LCAKICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlciIsIG1vZGUgPSAibWFya2VycyIsIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDMpKSAlPiUKICAgICBsYXlvdXQodGl0bGUgPSAiVmlzdWFsaXppbmcgUHJpbmNpcGxlIENvbXBvbmVudHMiKQpwCmBgYAoKRnJvbSB0aGUgZ3JhcGggYWJvdmUsIHdlIGNhbiBzZWUgdGhhdDoKCi0gaXQgY291bGQgYmUgZGlmZmljdWx0IHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gKipTdGFuZGluZyoqIGFuZCAqKlNpdHRpbmcqKiBhcyB0aGVyZSBhcmUgbGFyZ2Ugb3ZlcmxhcHMgaW4gdGhlaXIgc2Vuc29yIGRhdGEuCi0gKipMYXlpbmcqKiBoYXMgaXRzIG93biBjbHVzdGVyIHNvIGl0IHNob3VsZCBiZSBlYXN5IHRvIGNsYXNzaWZ5LgotICoqV2Fsa2luZyoqLCAqKldhbGtpbmcgVXBzdGFpcnMqKiBhbmQgKipXYWxraW5nIERvd25zdGFpcnMqKiBhcmUgdW5kZXJzdGFuZGFibHkgY2xvc2VyIHRvIGVhY2ggb3RoZXIgeWV0IHRoZXkgYXJlIHF1aXRlIGRpZmZlcmVudCB0byAqKlNpdHRpbmcqKiwgKipTdGFuZGluZyoqIGFuZCAqKkxheWluZyoqLgoKCjxicj4KCiMjIFN0ZXAgNCAtIEJ1aWxkIGFuZCBldmFsdXRhdGUgYSBwcmVkaWN0aXZlIG1vZGVsIHVzaW5nIEgyTydzIEdyYWRpZW50IEJvb3N0aW5nIE1hY2hpbmUgKEdCTSkgYWxnb3JpdGhtCgpgYGB7cn0KIyBEZWZpbmUgdGFyZ2V0IGFuZCBmZWF0dXJlcyBmb3IgbW9kZWwgdHJhaW5pbmcKdGFyZ2V0IDwtICJhY3Rpdml0eSIKZmVhdHVyZXMgPC0gc2V0ZGlmZihjb2xuYW1lcyhoZXhfdHJhaW4pLCB0YXJnZXQpICMgaS5lLiB1c2luZyB0aGUgcmVjb3JkcyBvZiBhbGwgNTYxIHNlbnNvcnMKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBCdWlsZCBhIEdCTSBtb2RlbCB3aXRoIGNyb3NzLXZhbGlkYXRpb24gYW5kIGVhcmx5IHN0b3BwaW5nCm1vZGVsIDwtIGgyby5nYm0oeCA9IGZlYXR1cmVzLAogICAgICAgICAgICAgICAgIHkgPSB0YXJnZXQsCiAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSBoZXhfdHJhaW4sICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9ICJoMm9fZ2JtIiwKICAgICAgICAgICAgICAgICBudHJlZXMgPSA1MDAsCiAgICAgICAgICAgICAgICAgbGVhcm5fcmF0ZSA9IDAuMDUsCiAgICAgICAgICAgICAgICAgbGVhcm5fcmF0ZV9hbm5lYWxpbmcgPSAwLjk5OSwKICAgICAgICAgICAgICAgICBtYXhfZGVwdGggPSA3LAogICAgICAgICAgICAgICAgIHNhbXBsZV9yYXRlID0gMC45LAogICAgICAgICAgICAgICAgIGNvbF9zYW1wbGVfcmF0ZSA9IDAuOSwKICAgICAgICAgICAgICAgICBuZm9sZHMgPSAzLAogICAgICAgICAgICAgICAgIGZvbGRfYXNzaWdubWVudCA9ICJTdHJhdGlmaWVkIiwKICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAibG9nbG9zcyIsCiAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gNSwKICAgICAgICAgICAgICAgICBzY29yZV90cmVlX2ludGVydmFsID0gMTAsCiAgICAgICAgICAgICAgICAgc2VlZCA9IDEyMzQpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG89RkFMU0V9CiMgSGlkZGVuIHN0ZXAKIyBVc2UgcHJlLXRyYWluZWQgbW9kZWwgaWYgaXQgZXhpc3RzCmNoa19tb2RlbCA8LSBzdXBwcmVzc01lc3NhZ2VzKGZpbGUuZXhpc3RzKCIuL21vZGVscy9oMm9fZ2JtIikpCgppZiAoY2hrX21vZGVsKSB7CiAgbW9kZWwgPC0gaDJvLmxvYWRNb2RlbCgiLi9tb2RlbHMvaDJvX2dibSIpCn0gZWxzZSB7CiAgbW9kZWwgPC0gaDJvLmdibSh4ID0gZmVhdHVyZXMsCiAgICAgICAgICAgICAgICAgeSA9IHRhcmdldCwKICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IGhleF90cmFpbiwgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gImgyb19nYm0iLAogICAgICAgICAgICAgICAgIG50cmVlcyA9IDUwMCwKICAgICAgICAgICAgICAgICBsZWFybl9yYXRlID0gMC4wNSwKICAgICAgICAgICAgICAgICBsZWFybl9yYXRlX2FubmVhbGluZyA9IDAuOTk5LAogICAgICAgICAgICAgICAgIG1heF9kZXB0aCA9IDcsCiAgICAgICAgICAgICAgICAgc2FtcGxlX3JhdGUgPSAwLjksCiAgICAgICAgICAgICAgICAgY29sX3NhbXBsZV9yYXRlID0gMC45LAogICAgICAgICAgICAgICAgIG5mb2xkcyA9IDMsCiAgICAgICAgICAgICAgICAgZm9sZF9hc3NpZ25tZW50ID0gIlN0cmF0aWZpZWQiLAogICAgICAgICAgICAgICAgIHN0b3BwaW5nX21ldHJpYyA9ICJsb2dsb3NzIiwKICAgICAgICAgICAgICAgICBzdG9wcGluZ19yb3VuZHMgPSA1LAogICAgICAgICAgICAgICAgIHNjb3JlX3RyZWVfaW50ZXJ2YWwgPSAxMCwKICAgICAgICAgICAgICAgICBzZWVkID0gMTIzNCkKfQpgYGAKCgpgYGB7cn0KIyBQcmludCBvdXQgbW9kZWwgc3VtbWFyeQptb2RlbApgYGAKCgpgYGB7cn0KIyBMb29rIGF0IHZhcmlhYmxlIGltcG9ydGFuY2UgaW4gdGhpcyBHQk0gbW9kZWwKaDJvLnZhcmltcChtb2RlbCkKYGBgCgpgYGB7cn0KIyBWaXN1YWxpemUgdmFyaWFibGUgaW1wb3J0YW5jZQpoMm8udmFyaW1wX3Bsb3QobW9kZWwsIG51bV9vZl9mZWF0dXJlcyA9IDE1KQpgYGAKCjxicj4KCiMjIFN0ZXAgNSAtIE1ha2UgYW5kIGV2YWx1dGF0ZSBwcmVkaWN0aW9ucwoKYGBge3J9CiMgTWFrZSBwcmVkaWN0aW9ucwp5aGF0X3Rlc3QgPC0gaDJvLnByZWRpY3QobW9kZWwsIGhleF90ZXN0KQpoZWFkKHloYXRfdGVzdCkKYGBgCgpgYGB7cn0KIyBFdmFsdWF0ZSBwcmVkaWN0aW9ucwpoMm8ucGVyZm9ybWFuY2UobW9kZWwsIG5ld2RhdGEgPSBoZXhfdGVzdCkKYGBgCgpBcyBleHBlY3RlZDoKLSBJdCBpcyBlYXN5IHRvIGNsYXNzaWZ5ICoqTGF5aW5nKioKLSBJdCBpcyBkaWZmaWN1bHQgdG8gZGlzdGluZ3Vpc2ggYmV0d2VlbiAqKlNpdHRpbmcqKiBhbmQgKipTdGFuZGluZyoqCgoKPGJyPgoKIyMgU3RlcCA2IC0gRXhwb3J0IHRoZSBQQ0EgYW5kIEdCTSBtb2RlbHMgZm9yIFNoaW55IGFwcGxpY2F0aW9ucwoKYGBge3IsIGV2YWw9RkFMU0V9Cmgyby5zYXZlTW9kZWwobW9kZWxfcGNhLCBwYXRoID0gIi4vbW9kZWxzIikKaDJvLnNhdmVNb2RlbChtb2RlbCwgcGF0aCA9ICIuL21vZGVscyIpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG89RkFMU0V9CmNoa19nYm0gPC0gc3VwcHJlc3NNZXNzYWdlcyhmaWxlLmV4aXN0cygiLi9tb2RlbHMvaDJvX2dibSIpKQpjaGtfcGNhIDwtIHN1cHByZXNzTWVzc2FnZXMoZmlsZS5leGlzdHMoIi4vbW9kZWxzL2gyb19wY2EiKSkKaWYgKCFjaGtfZ2JtKSBoMm8uc2F2ZU1vZGVsKG1vZGVsLCBwYXRoID0gIi4vbW9kZWxzIikKaWYgKCFjaGtfcGNhKSBoMm8uc2F2ZU1vZGVsKG1vZGVsX3BjYSwgcGF0aCA9ICIuL21vZGVscyIpCmBgYAoKPGJyPgoKCg==